package com.qingzhuge.manager.security.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author zeroxiao
 * @date 2019/9/12 08:55
 */
@Slf4j
public class RedisTokenRepositoryImpl implements PersistentTokenRepository {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RedisSerializer<String> stringRedisSerializer;
    @Autowired
    private RedisConnectionFactory connectionFactory;

    /**
     * token有效时间10天
     * 框架实现  RememberMeConfigurer#tokenValiditySeconds
     * 此处使用redis实现
     */
    private static final Long TOKEN_VALID_DAYS = 10L;

    @Override
    public void createNewToken(PersistentRememberMeToken token) {
        if (log.isDebugEnabled()) {
            log.debug("token create seriesId: [{}]", token.getSeries());
        }
        String key = generateKey(token.getSeries());
        HashMap<String, String> map = new HashMap<>(16);
        map.put("username", token.getUsername());
        map.put("tokenValue", token.getTokenValue());
        map.put("date", String.valueOf(token.getDate().getTime()));
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS);
    }

    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        String key = generateKey(series);
        HashMap<String, String> map = new HashMap<>(16);
        map.put("tokenValue", tokenValue);
        map.put("date", String.valueOf(lastUsed.getTime()));
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS);
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
        String key = generateKey(seriesId);
        List<String> hashKeys = new ArrayList<>();
        hashKeys.add("username");
        hashKeys.add("tokenValue");
        hashKeys.add("date");
        HashOperations<String, String, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash();
        List<Object> hashValues = stringObjectObjectHashOperations.multiGet(key, hashKeys);
        String username = (String) hashValues.get(0);
        String tokenValue = (String) hashValues.get(1);
        String date = (String) hashValues.get(2);
        if (null == username || null == tokenValue || null == date) {
            return null;
        }
        long timestamp = Long.parseLong(date);
        Date time = new Date(timestamp);
        return new PersistentRememberMeToken(username, seriesId, tokenValue, time);
    }

    @Override
    public void removeUserTokens(String username) {
        if (log.isDebugEnabled()) {
            log.debug("token remove username: [{}]", username);
        }
        RedisConnection redisConnection = connectionFactory.getConnection();
        byte[] usernames = stringRedisSerializer.serialize("username");
        try (Cursor<byte[]> cursor = redisConnection.scan(ScanOptions.scanOptions().match(generateKey("*")).count(1024).build())) {
            while (cursor.hasNext()) {
                byte[] key = cursor.next();
                byte[] hashValue;
                if (usernames != null) {
                    hashValue = redisConnection.hGet(key, usernames);
                    String storeName = (String) redisTemplate.getHashValueSerializer().deserialize(hashValue);
                    if (username.equals(storeName)) {
                        redisConnection.expire(key, 0L);
                        return;
                    }
                }
            }
        } catch (IOException ex) {
            log.warn("token remove exception", ex);
        }
    }

    /**
     * 生成key
     */
    private String generateKey(String series) {
        return String.format("qingzhuge:security:rememberMe:token:%s", series);
    }
}
